﻿using System;
using System.Collections.Generic;
using System.Linq;
//using LinqKit;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using EttainGroup.Filters.Extensions;

namespace EttainGroup.Filters
{
    /// <summary>
    /// this class provides data utility functions for our repositories
    /// </summary>
    public class Utility
    {
        /// <summary>
        /// OK.  This method provides the functionality to build a lambda expression from values configured in a data store
        /// This allows the application to pre-build filter expressions based on a user's configuration and use those expressions
        /// when performing fetch operations against the repository.  The way the data is currently structured dictates that 
        /// a filter is a particular type and field name.  A user has a set of values that are valid for them as they apply to that
        /// entity and field name.  They are stored as reference id's in the user's filter values.  The filter values will be compiled
        /// as Or's into the expression.  This expression can be AND'd together with other expressions outside this function.
        /// </summary>
        /// <typeparam name="T">The Type of entity that we want to build our expression for</typeparam>
        /// <param name="userId">The user that we want to build the expressions for</param>
        /// <returns>A LINQ Expression for the passed-in Type that uses values stored for the user in the configuration data store</returns>
        public static Expression<Func<T, bool>> BuildPredicate<T>(IFilterRepository filterRepo, Expression<Func<T, bool>> expression, string componentName, string groupId)
        {

            //set the default predicate to true (for inclusion, as we are anding the outer filters)...
            Expression<Func<T, bool>> predicate = expression == null ? t => true : expression;

            // get the name of the type
            string typeName = typeof(T).Namespace + "." + typeof(T).Name;

            // get the filters that have been applied to the user for this type
            var applicableFilters = filterRepo.FiltersForExpression(typeName, componentName, groupId);

            foreach (var filter in applicableFilters)
            {
                Expression<Func<T, bool>> filterPred = t => false;
                // begin the parameter expression (this is where the lambda will be created)
                ParameterExpression e = Expression.Parameter(typeof(T), "e");

                // get the filter values
                var filterValues = filter.Values;

                // I need to and between fields and or within them.  so to do that I will set a current propert string
                // when it changes, I will and.  if it's the same I will or the expression together.
                string currentProperty = string.Empty;

                foreach (var fval in filterValues)
                {

                    string propertyName = filter.PropertyName;
                    string[] propertyNames = propertyName.Split(".".ToCharArray());
                    Expression property = null;
                    if (propertyNames.Length > 1)
                    {
                        property = Expression.Property(Expression.Property(e, propertyNames[0]), propertyNames[1]);
                    }
                    else
                    {
                        property = Expression.Property(e, propertyName);
                    }
                    // apply the property of the type to the expression

                    switch (filter.ValueType)
                    {

                        case FilterValueType.Int:
                            Expression intExpression = Expression.Constant(Convert.ToInt32(fval.Value));
                            switch (filter.ComparisonType)
	                        {
                                case FilterComparisonType.Equals:
                                    
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionEqual(property, intExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThan(property, intExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, intExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThan(property, intExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThanEqual(property, intExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.Between:
                                    Expression secondary = Expression.Constant(Convert.ToInt32(fval.Secondary));
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, intExpression), e) as Expression<Func<T, bool>>));
                                    filterPred = filterPred.And((Expression.Lambda(ExpressionLessThanEqual(property, secondary), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.NotEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionNotEqual(property, intExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                default:
                                    break;
	                        }
                            break;
                        case FilterValueType.String:
                            Expression stringExpression = Expression.Constant(fval.Value);
                            switch (filter.ComparisonType)
	                        {
                                case FilterComparisonType.Equals:
                                    filterPred = filterPred.Or((Expression.Lambda(Expression.Equal(property, stringExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.StartsWith:
                                    MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string), typeof(StringComparison) }, null);
                                    filterPred = filterPred.Or(Expression.Lambda(Expression.Call(property, mi, Expression.Constant(fval.Value, typeof(string)), Expression.Constant(StringComparison.CurrentCultureIgnoreCase, typeof(StringComparison))), e) as Expression<Func<T, bool>>);
                                    break;
                                case FilterComparisonType.Contains:
                                    MethodInfo cmi = typeof(string).GetMethod("Contains", new Type[] { typeof(string) }, null);
                                    filterPred = filterPred.Or(Expression.Lambda(Expression.Call(property, cmi, Expression.Constant(fval.Value, typeof(string))), e) as Expression<Func<T, bool>>);
                                    break;
                                case FilterComparisonType.NotEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(Expression.NotEqual(property, stringExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                default:
                                    break;
	                        }
                            break;
                        case FilterValueType.Decimal:
                            Expression decExpression = Expression.Constant(Convert.ToDecimal(fval.Value));
                            switch (filter.ComparisonType)
	                        {
                                case FilterComparisonType.Equals:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionEqual(property, decExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThan(property, decExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, decExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThan(property, decExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThanEqual(property, decExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.Between:
                                    Expression secondaryDec = Expression.Constant(Convert.ToDecimal(fval.Secondary));
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, decExpression), e) as Expression<Func<T, bool>>));
                                    filterPred = filterPred.And((Expression.Lambda(ExpressionLessThanEqual(property, secondaryDec), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.NotEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionNotEqual(property, decExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                default:
                                    break;
	                        }
                            break;
                        case FilterValueType.Long:
                            Expression dblExpression = Expression.Constant(Convert.ToDouble(fval.Value));
                            switch (filter.ComparisonType)
	                        {
                                case FilterComparisonType.Equals:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionEqual(property, dblExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThan(property, dblExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, dblExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThan(property, dblExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThanEqual(property, dblExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.Between:
                                    Expression secondaryDbl = Expression.Constant(Convert.ToDouble(fval.Secondary));
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, dblExpression), e) as Expression<Func<T, bool>>));
                                    filterPred = filterPred.And((Expression.Lambda(ExpressionLessThanEqual(property, secondaryDbl), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.NotEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionNotEqual(property, dblExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                default:
                                    break;
	                        }
                            break;
                        case FilterValueType.DateTime:
                            Expression dtExpression = Expression.Constant(Convert.ToDateTime(fval.Value));
                            switch (filter.ComparisonType)
	                        {
                                
                                case FilterComparisonType.Equals:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionEqual(property, dtExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThan(property, dtExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.GreaterThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, dtExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThan:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThan(property, dtExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.LessThanEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionLessThanEqual(property, dtExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.Between:
                                    Expression secondaryDt = Expression.Constant(Convert.ToDateTime(fval.Secondary));
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionGreaterThanEqual(property, dtExpression), e) as Expression<Func<T, bool>>));
                                    filterPred = filterPred.And((Expression.Lambda(ExpressionLessThanEqual(property, secondaryDt), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.NotEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionNotEqual(property, dtExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                default:
                                    break;
	                        }
                            break;
                        case FilterValueType.Boolean:
                            Expression bExpression = Expression.Constant(Convert.ToBoolean(fval.Value.ToLower()));
                            switch (filter.ComparisonType)
	                        {
                                
                                case FilterComparisonType.Equals:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionEqual(property, bExpression), e) as Expression<Func<T, bool>>));
                                    break;
                                case FilterComparisonType.NotEqual:
                                    filterPred = filterPred.Or((Expression.Lambda(ExpressionNotEqual(property, bExpression), e) as Expression<Func<T, bool>>));
                                    break;
	                        }
                            break;
                        default:
                            break;
                    }

                }
                predicate = predicate.And(filterPred);
            }
            // when done return the OR'd values out as one expression

            return predicate;
        }

        private static bool IsNullableType(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        private static Expression ExpressionEqual(Expression e1, Expression e2)
        {
            if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
                e2 = Expression.Convert(e2, e1.Type);
            else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
                e1 = Expression.Convert(e1, e2.Type);
            return Expression.Equal(e1, e2);
        }

        private static Expression ExpressionGreaterThan(Expression e1, Expression e2)
        {
            if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
                e2 = Expression.Convert(e2, e1.Type);
            else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
                e1 = Expression.Convert(e1, e2.Type);
            return Expression.GreaterThan(e1, e2);
        }

        private static Expression ExpressionGreaterThanEqual(Expression e1, Expression e2)
        {
            if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
                e2 = Expression.Convert(e2, e1.Type);
            else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
                e1 = Expression.Convert(e1, e2.Type);
            return Expression.GreaterThanOrEqual(e1, e2);
        }
        private static Expression ExpressionLessThan(Expression e1, Expression e2)
        {
            if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
                e2 = Expression.Convert(e2, e1.Type);
            else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
                e1 = Expression.Convert(e1, e2.Type);
            return Expression.LessThan(e1, e2);
        }

        private static Expression ExpressionLessThanEqual(Expression e1, Expression e2)
        {
            if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
                e2 = Expression.Convert(e2, e1.Type);
            else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
                e1 = Expression.Convert(e1, e2.Type);
            return Expression.LessThanOrEqual(e1, e2);
        }

        private static Expression ExpressionNotEqual(Expression e1, Expression e2)
        {
            if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
                e2 = Expression.Convert(e2, e1.Type);
            else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
                e1 = Expression.Convert(e1, e2.Type);
            return Expression.NotEqual(e1, e2);
        }


    }
}

